/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package bgu.sonar.util.buffers;

import java.io.IOException;
import java.io.InputStream;

/**
 *
 * @author Administrator
 */
public class CircularByteBuffer {

    int start;
    int taken = 0;
    byte[] buffer;

    public CircularByteBuffer(int capacity) {
        buffer = new byte[capacity];
        reset();
    }

    public void reset() {
        start = 0;
        taken = 0;
    }

    /**
     * read from the given input stream into this buffer - this method can block.
     * ** notice that there is no guarantee that the buffer will successfully read any data!
     *
     * @param in
     * @return the number of bytes read or -1 if eos reached
     * @throws IOException
     */
    public int readFrom(InputStream in) throws IOException {
        if (isFull()) {
            return 0;
        }
        boolean fliped = isFlipped();
        int r;
        if (fliped) {
            int s = (start + taken) % buffer.length;
            r = in.read(buffer, s, start - s);
        } else {
            r = in.read(buffer, start + taken, buffer.length - start - taken);
        }

        taken += (r > 0 ? r : 0);
        return r;
    }

    /**
     * @return true if this buffer is in flipped state (mean the start is after the end)
     */
    private boolean isFlipped() {
        return start + taken > buffer.length;
    }

    /**
     * @return true if this buffer is empty
     */
    public boolean isEmpty() {
        return taken == 0;
    }

    /**
     * @return the amount of bytes that can be inserted into this buffer
     */
    public int freeSpace() {
        return buffer.length - taken;
    }

    /**
     * @return the number of bytes that can be read from this buffer
     */
    public int taken() {
        return taken;
    }

    public int maximumCapacity() {
        return buffer.length;
    }

    public boolean isFull() {
        return freeSpace() == 0;
    }

    /**
     * if this buffer is empty will throw EmptyContainerException
     *
     * @return
     */
    public byte next() {
        if (isEmpty()) {
            throw new InvalidBufferStateException("buffer is empty");
        }
        byte n = buffer[start];
        start = (start + 1) % buffer.length;
        taken--;
        return n;
    }

    /**
     * if container is full will throw FullContainerException
     *
     * @param b
     */
    public void insert(byte b) {
        if (isFull()) {
            throw new InvalidBufferStateException("buffer is full");
        }
        buffer[(start + taken) % buffer.length] = b;
        taken++;
    }

    /**
     * copy the given buffer into an array and return it - the buffer is then being reseted
     *
     * @return
     */
    public byte[] dump() {
        byte[] ret = new byte[taken];
        if (isFlipped()) {
            System.arraycopy(buffer, start, ret, 0, buffer.length - start);
            System.arraycopy(buffer, 0, ret, buffer.length - start, taken - buffer.length + start);
        } else {
            System.arraycopy(buffer, start, ret, 0, taken);
        }

        reset();
        return ret;
    }

    public static class InvalidBufferStateException extends RuntimeException {

        public InvalidBufferStateException() {
        }

        public InvalidBufferStateException(String message) {
            super(message);
        }

        public InvalidBufferStateException(String message, Throwable cause) {
            super(message, cause);
        }

        public InvalidBufferStateException(Throwable cause) {
            super(cause);
        }
    }
}
